/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.openide.explorer;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.text.MessageFormat;
import org.openide.util.datatransfer.*;
import org.openide.loaders.*;
import org.openide.NotifyDescriptor;
import org.openide.TopManager;
import org.openide.filesystems.*;
import org.openide.actions.*;
/*import org.openide.actions.CutAction;
import org.openide.actions.CopyAction;
import org.openide.actions.PasteAction;
import org.openide.actions.DeleteAction;*/
import org.openide.util.actions.ActionPerformer;
import org.openide.nodes.Node;
import org.openide.util.RequestProcessor;
/** This class contains the default implementation of reactions to the standard
* explorer actions. It can be
* attached to any {@link ExplorerManager}. Then this class will listen to changes
* of selected nodes or the explored context of that manager, and update the state
* of cut/copy/paste/delete actions.
* <P>
* An instance of this class can only be attached to one manager at a time. Use
* {@link #attach} and {@link #detach} to make the connection.
*
* @author Jan Jancura, Petr Hamernik, Ian Formanek, Jaroslav Tulach
*/
public class ExplorerActions extends Object {
/** copy action performer */
private ActionPerformer copyActionPerformer;
/** cut action performer */
private ActionPerformer cutActionPerformer;
/** delete action performer */
private ActionPerformer deleteActionPerformer;
/** tracker for all actions */
private Listener listener;
/** the manager we are listening on */
ExplorerManager manager;
/** must the delete be confirmed */
private boolean confirmDelete = true;
/** actions to work with */
static CopyAction copy = null;
static CutAction cut = null;
static DeleteAction delete = null;
static PasteAction paste = null;
/** Attach to new manager.
* @param m the manager to listen on
*/
public synchronized void attach (ExplorerManager m) {
if (manager != null) {
// first of all detach
detach ();
}
manager = m;
if (listener == null) listener = new Listener ();
manager.addPropertyChangeListener (listener);
ExClipboard clip = TopManager.getDefault().getClipboard();
clip.addClipboardListener(listener);
updateActions ();
}
/** Detach from manager currently being listened on.
*/
public synchronized void detach () {
if (manager == null) return;
manager.removePropertyChangeListener (listener);
ExClipboard clip = TopManager.getDefault().getClipboard();
clip.removeClipboardListener(listener);
stopActions ();
manager = null;
}
/** Set whether to confirm deletions.
* @param yes <code>true</code> to confirm deletions
*/
public final void setConfirmDelete (boolean yes) {
confirmDelete = yes;
}
/** Should deletions be confirmed?
* @return <code>true</code> if deletions must be confirmed
*/
public final boolean isConfirmDelete () {
return confirmDelete;
}
/** Stops listening on all actions
*/
void stopActions () {
if (copyActionPerformer != null) {
copy.setActionPerformer (null);
cut.setActionPerformer (null);
paste.setPasteTypes (null);
delete.setActionPerformer (null);
}
}
/** Updates the state of all actions.
* @param path list of selected nodes
*/
void updateActions () {
Node[] path = (manager == null)? (new Node[0]) : (manager.getSelectedNodes ());
/* System.out.println("ExplorerActions.updateActions: "+path.length);
for (int i = 0; i < path.length; i++)
System.out.println("actions["+i+"]="+path[i].getDisplayName ());
Thread.dumpStack(); */
if (copyActionPerformer == null) {
copyActionPerformer = new CopyCutActionPerformer (true);
cutActionPerformer = new CopyCutActionPerformer (false);
deleteActionPerformer = new DeleteActionPerformer();
copy = new CopyAction ();
cut = new CutAction ();
paste = new PasteAction ();
delete = new DeleteAction();
}
int i;
int k = path != null ? path.length : 0;
if (k > 0) {
for (i = 0; i < k; i++) {
if (!path[i].canCopy ()) {
copy.setActionPerformer (null);
break;
}
}
if (i == k) copy.setActionPerformer (copyActionPerformer);
for (i = 0; i < k; i++) {
if (!path[i].canCut ()) {
cut.setActionPerformer (null);
break;
}
}
if (i == k) cut.setActionPerformer (cutActionPerformer);
for (i = 0; i < k; i++) {
if (!path[i].canDestroy ()) {
delete.setActionPerformer (null);
break;
}
}
if (i == k) delete.setActionPerformer (deleteActionPerformer);
} else {
copy.setActionPerformer (null);
cut.setActionPerformer (null);
delete.setActionPerformer (null);
}
updatePasteAction(path);
}
/** Updates paste action.
* @param path selected nodes
*/
private void updatePasteAction (Node[] path) {
ExplorerManager man = manager;
if (man == null) {
paste.setPasteTypes (null);
return;
}
if (path != null && ((path.length > 1) ||
((path.length == 1) && (path [0].isLeaf())))
) {
paste.setPasteTypes(null);
return;
} else {
Node pan = man.getExploredContext ();
Node[] selectedNodes = man.getSelectedNodes ();
if (selectedNodes != null && (selectedNodes.length == 1) && (!selectedNodes[0].isLeaf()))
pan = selectedNodes[0];
Clipboard clipboard = TopManager.getDefault().getClipboard();
Transferable trans = clipboard.getContents(TopManager.getDefault()); //hahaha !! who is the requestor ??!
if (trans != null) {
// First, just ask the node if it likes this transferable, whatever it may be.
// If it does, then fine.
PasteType[] pasteTypes = pan.getPasteTypes(trans);
if (pasteTypes.length != 0) {
paste.setPasteTypes(pasteTypes);
return;
}
boolean flavorSupported = false;
try {
flavorSupported = trans.isDataFlavorSupported(ExTransferable.multiFlavor);
} catch (java.lang.Exception e) {
// patch to get the Netbeans start under Solaris
// [PENDINGworkaround]
}
if (flavorSupported) {
// The node did not accept this multitransfer as is--try to break it into
// individual transfers and paste them in sequence instead.
try {
MultiTransferObject obj = (MultiTransferObject) trans.getTransferData(ExTransferable.multiFlavor);
int count = obj.getCount();
boolean ok = true;
Transferable[] t = new Transferable[count];
PasteType[] p = new PasteType[count];
for (int i = 0; i < count; i++) {
t[i] = obj.getTransferableAt(i);
pasteTypes = pan.getPasteTypes(t[i]);
if (pasteTypes.length == 0) {
ok = false;
break;
}
// [PENDING] this is ugly! ideally should be some way of comparing PasteType's for similarity?
p[i] = pasteTypes[0];
}
if (ok) {
paste.setPasteTypes(new PasteType[] { new MultiPasteType(t, p) } );
return;
}
}
catch (UnsupportedFlavorException e) {
}
catch (IOException e) {
}
}
}
if (paste != null) paste.setPasteTypes(null);
return;
}
}
/** Paste type used when in clipbopard is MultiTransferable */
private static class MultiPasteType extends PasteType {
/** Array of transferables */
Transferable[] t;
/** Array of paste types */
PasteType[] p;
/** Constructs new MultiPasteType for the given content of the clipboard */
MultiPasteType(Transferable[] t, PasteType[] p) {
this.t = t;
this.p = p;
}
/** Performs the paste action.
* @return Transferable which should be inserted into the clipboard after
* paste action. It can be null, which means that clipboard content
* should be cleared.
*/
public Transferable paste() throws IOException {
int size = p.length;
Transferable[] arr = new Transferable[size];
for (int i = 0; i < size; i++) {
Transferable newTransferable = p[i].paste();
if (newTransferable != null) {
arr[i] = newTransferable;
} else {
// keep the orginal
arr[i] = t[i];
}
}
return new ExTransferable.Multi (arr);
}
}
/** Class which performs copy and cut actions */
class CopyCutActionPerformer extends Object implements
org.openide.util.actions.ActionPerformer {
/** determine if adapter is used for copy or cut action. */
boolean copyCut;
/** Create new adapter */
public CopyCutActionPerformer (boolean b) {
copyCut = b;
}
/** Perform copy or cut action. */
public void performAction(org.openide.util.actions.SystemAction action) {
Transferable trans = null;
Node[] sel = manager.getSelectedNodes ();
if (sel.length != 1) {
Transferable[] arrayTrans = new Transferable[sel.length];
for (int i = 0; i < sel.length; i++)
if ((arrayTrans[i] = getTransferableOwner(sel[i])) == null) return;
trans = new ExTransferable.Multi (arrayTrans);
}
else {
trans = getTransferableOwner(sel[0]);
}
if (trans != null) {
Clipboard clipboard = TopManager.getDefault().getClipboard();
clipboard.setContents(trans, new StringSelection ("")); // NOI18N
}
}
private Transferable getTransferableOwner(Node node) {
try {
return copyCut ? node.clipboardCopy() : node.clipboardCut();
} catch (java.io.IOException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
org.openide.TopManager.getDefault().notify(
new org.openide.NotifyDescriptor.Exception(e)
);
return null;
}
}
}// innerclass CopyCutActionAdapter
/** Class which performs delete action */
class DeleteActionPerformer extends Object implements
org.openide.util.actions.ActionPerformer {
/** Perform delete action. */
public void performAction(org.openide.util.actions.SystemAction action) {
final Node[] sel = manager.getSelectedNodes ();
if ((sel == null) || (sel.length == 0))
return;
boolean isConfirmed = !confirmDelete;
// ask for confirmation if not confirmed
if (isConfirmed == false) {
Object params;
String title;
MessageFormat mf;
if (sel.length == 1) {
if (sel[0].getCookie (DataShadow.class) != null) {
mf = new MessageFormat (
ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteShadow" // NOI18N
)
);
title = ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteShadowTitle" // NOI18N
);
DataShadow obj = (DataShadow)sel[0].getCookie (DataShadow.class);
params = new Object[] {
obj.getName (), // name of the shadow
sel[0].getDisplayName (), // name of original
obj.getPrimaryFile ().toString (), // full name of file for shadow
obj.getOriginal ().getPrimaryFile ().toString () // full name of original file
};
} else if (sel[0].getCookie(DataFolder.class) != null) {
mf = new MessageFormat(
ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteFolder" // NOI18N
)
);
title = ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteFolderTitle" // NOI18N
);
params = new Object[] { sel[0].getDisplayName() };
} else {
mf = new MessageFormat(
ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteObject" // NOI18N
)
);
title = ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteObjectTitle" // NOI18N
);
params = new Object[] { sel[0].getDisplayName() };
}
}
else {
mf = new MessageFormat(ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteObjects" // NOI18N
));
title = ExplorerManager.explorerBundle.getString(
"MSG_ConfirmDeleteObjectsTitle" // NOI18N
);
params = new Object[] { new Integer(sel.length) };
}
NotifyDescriptor desc = new NotifyDescriptor.Confirmation(
mf.format(params),
title,
NotifyDescriptor.YES_NO_OPTION
);
isConfirmed = NotifyDescriptor.YES_OPTION.equals (
TopManager.getDefault().notify(desc)
);
}
// perform action if confirmed
if (isConfirmed) {
// clear selected nodes
try {
manager.setSelectedNodes(new Node[] {});
} catch (java.beans.PropertyVetoException e) {
// never thrown, setting empty selected nodes cannot be vetoed
}
try {
TopManager.getDefault().getRepository ().getDefaultFileSystem().runAtomicAction (new FileSystem.AtomicAction () {
public void run () {
for (int i=0; i<sel.length; i++) {
try {
sel[i].destroy ();
} catch (java.io.IOException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
org.openide.TopManager.getDefault().notify(
new org.openide.NotifyDescriptor.Exception(e)
);
}
}
}
});
}
catch (IOException e) {
}
delete.setActionPerformer (null); // fixes bug #673
}
}
}// innerclass DeleteActionAdapter
/** delay for updating actions */
private static final int DELAY = 150;
private class Listener
implements PropertyChangeListener, ClipboardListener, Runnable {
/** a task for updating with delay */
private RequestProcessor.Task task = RequestProcessor.createRequest(this);
{
// priority on the level of AWT
task.setPriority (Thread.MAX_PRIORITY - 1);
}
public void propertyChange (PropertyChangeEvent e) {
task.schedule (DELAY);
}
/** This method is called when content of clipboard is changed.
* @param ev event describing the action
*/
public void clipboardChanged (ClipboardEvent ev) {
if (!ev.isConsumed ()) {
updatePasteAction(manager.getSelectedNodes ());
}
}
/** Updates the actions.
*/
public void run() {
updateActions ();
}
}
}
/*
* Log
* 21 Gandalf-post-FCS1.19.1.0 3/28/00 Svatopluk Dedic notifyException replaced
* with NotifyDescriptor.Exception
* 20 Gandalf 1.19 1/16/00 Ian Formanek NOI18N
* 19 Gandalf 1.18 1/15/00 Jaroslav Tulach DataShadow enhancements
* 18 Gandalf 1.17 1/12/00 Ian Formanek NOI18N
* 17 Gandalf 1.16 12/9/99 Jaroslav Tulach #4930
* 16 Gandalf 1.15 12/2/99 Jaroslav Tulach Performance
* improvements.
* 15 Gandalf 1.14 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 14 Gandalf 1.13 9/20/99 Jesse Glick Permitting a node to
* explicitly accept a multi-object paste transferable as a single unit if
* it wishes, rather than considering each transferable component
* separately.
* 13 Gandalf 1.12 8/13/99 Martin Entlicher VCS TEST
* 12 Gandalf 1.11 8/13/99 Martin Entlicher VCS Test
* 11 Gandalf 1.10 8/13/99 Martin Entlicher VCS Test
* 10 Gandalf 1.9 6/10/99 Ian Formanek more safe to possible
* problems
* 9 Gandalf 1.8 6/8/99 Ian Formanek ---- Package Change To
* org.openide ----
* 8 Gandalf 1.7 5/14/99 Jaroslav Tulach Fix 1739
* 7 Gandalf 1.6 4/2/99 Jaroslav Tulach
* 6 Gandalf 1.5 3/20/99 Jesse Glick [JavaDoc]
* 5 Gandalf 1.4 3/19/99 Jaroslav Tulach TopManager.getDefault
* ().getRegistry ()
* 4 Gandalf 1.3 2/25/99 Jaroslav Tulach Change of clipboard
* management
* 3 Gandalf 1.2 2/11/99 Ian Formanek Renamed FileSystemPool
* -> Repository
* 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of
* DataObject
* 1 Gandalf 1.0 1/5/99 Ian Formanek
* $
* Beta Change History:
* 0 Tuborg 0.19 --/--/98 Petr Hamernik Paste action redesigned
* 0 Tuborg 0.20 --/--/98 Jan Formanek System.out.println("ClipboardException") replaced with notifyException
* 0 Tuborg 0.21 --/--/98 Jan Formanek added compile action
* 0 Tuborg 0.30 --/--/98 Jan Formanek SWITCHED TO NODES
* 0 Tuborg 0.33 --/--/98 Jan Formanek bugfix
* 0 Tuborg 0.34 --/--/98 Jan Formanek another bugfix (ClipboardOperations)
* 0 Tuborg 0.35 --/--/98 Jaroslav Tulach ClipboardOperation moved to node.
* 0 Tuborg 0.36 --/--/98 Jan Formanek delete action support added
* 0 Tuborg 0.37 --/--/98 Jan Formanek compile action removed
* 0 Tuborg 0.38 --/--/98 Jaroslav Tulach does not react to change of clipboard if the event is consumed
* 0 Tuborg 0.39 --/--/98 Petr Hamernik save action
*/